﻿using CSRedis;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace WebAPI.Utils
{
    //CSRedisCore redis.io推荐使用
    //ServiceStack.Redis 是商业版，免费版有限制；
    //StackExchange.Redis 是免费版，但是内核在.NETCore 运行有问题经常 Timeout，暂无法解决；
    
    // 普通订阅
    //CacheHelper.Redis.Subscribe(("__keyevent@0__:expired", msg => Expired(msg.Body)));
    // 争抢订阅（只可一端收到消息）
    //CacheHelper.Redis.SubscribeList(new[] { RedisConst.SUB_ORDER, RedisConst.SUB_AUDIT, RedisConst.SUB_REFUND }, (key, msg) => Sub(key, msg));

    public class RedisUtil
    {
        //"192.168.3.42:6379,password=123456,defaultDatabase=0,poolsize=500,ssl=false,writeBuffer=10240,prefix=test_"
        private static CSRedisClient redisClient = new CSRedisClient("127.0.0.1:6379,defaultDatabase=0");

        //notify-keyspace-events Ex
        public Task PutJob<T>(TimeSpan delay, T jobData, Action<T> callback)
        {
            var guid = Guid.NewGuid().ToString("N");

            // Default Database
            var channel = $"__keyevent@{0}__:expired";

            redisClient.Set(guid, jobData, delay);
            redisClient.Subscribe((channel, new Action<CSRedisClient.SubscribeMessageEventArgs>(msg =>
            {
                if (msg.Body != guid) return;
                callback?.Invoke(jobData);
            })));
            return Task.CompletedTask;
        }

        // 过期时间
        public static void exprise()
        {
            var sub = redisClient.Subscribe(("__keyevent@0__:expired", cw =>
            {
                //KeyExpiredEvent(cw.Body);//过期的key
            }));
        }

        public static bool Set(string key, object data, TimeSpan? expiredTime)
        {
            if (expiredTime.HasValue)
                return redisClient.Set(key, data, expiredTime.Value);
            else
                return redisClient.Set(key, data);
        }

        public static void setValue()
        {
            //redis中的string存取
            redisClient.Set("name", "小明");
            var name = redisClient.Get("name");
            Console.WriteLine($"name={name}");

            redisClient.Set("birth", DateTime.Now);
            var birth = redisClient.Get<DateTime>("birth");
            Console.WriteLine($"birth={birth}");

            redisClient.Set("info", new { sex = "female", age = "20" });
            var info = redisClient.Get<object>("info");
            Console.WriteLine($"info={info}");

            Console.WriteLine("ok");
            Console.ReadLine();
        }

        //通用指令
        public static void common()
        {
            // 必须先初始化
            RedisHelper.Initialization(new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0"));

            //查找所有分区节点中符合给定模式(pattern)的 key
            string[] keyAll = RedisHelper.Keys("*");

            //以秒为单位，返回给定 key 的剩余生存时间
            long ttl1 = RedisHelper.Ttl("keyString1");

            //用于在 key 存在时删除 key
            long del1 = RedisHelper.Del("keyString1");

            //检查给定 key 是否存在
            bool isExists1 = RedisHelper.Exists("keyString1");

            //为给定 key 设置过期时间
            bool isExpire1 = RedisHelper.Expire("keyString1", 100);

            //为给定 key 设置过期时间
            RedisHelper.ExpireAt("keyString1", new DateTime(2021, 6, 11, 16, 0, 0));
        }

        //字符串
        public static void str()
        {
            // 设置指定 key 值，默认不过期
            bool set_string1 = RedisHelper.Set("keyString_String1", "测试值1");

            // 设置指定 key 值，并设置过期时间（单位：秒）
            bool set_string2 = RedisHelper.Set("keyString_String2", "测试值2", 1);

            // 获取指定 key 的值，不存在的 key，值返回null
            string get_string1 = RedisHelper.Get("keyString_String1");

            // 获取指定 key 的值，不存在的 key，或者指定的 key 不是int型，则返回int类型的默认值0
            int get_int1 = RedisHelper.Get<int>("keyString_String1");

            //对整数类型进行自增，自减操作
            bool set_int1 = RedisHelper.Set("keyString_Num1", "23");

            // 将 key 所储存的值加上指定的增量值（increment）
            long incrBy1 = RedisHelper.IncrBy("keyString_Num1", 2);// #25

            // 将 key 所储存的值加上指定的增量值（increment），负数就是减量值
            long incrBy2 = RedisHelper.IncrBy("keyString_Num1", -1);// #24

            //在指定 key 的 value 末尾追加字符串
            bool set_append1 = RedisHelper.Set("keyString_Append1", "qaz", 30);

            // 将指定的 value 追加到该 key 原来值（value）的末尾
            long append1 = RedisHelper.Append("keyString_Append1", "wsx");// #6 结果：key 中字符串的长度
        }

        //哈希
        public static void hash()
        {
            // 将哈希表 key 中的字段 field 的值设为 value
            bool set_hash_user1_uname = RedisHelper.HSet("User:10001", "uname", "gongyg"); // 冒号的作用相当于创建一个文件夹
            bool set_hash_user1_upwd = RedisHelper.HSet("User:10001", "upassword", "123456");
            bool set_hash_user1_uid = RedisHelper.HSet("User:10001", "uid", "12");

            // 获取存储在哈希表中指定字段的值
            string uName = RedisHelper.HGet("User:10001", "uname");

            // 获取存储在哈希表中指定字段的值，并指定类型
            int uId = RedisHelper.HGet<int>("User:10001", "uid");

            // 删除一个或多个哈希表字段，不能删除key
            long hDel1 = RedisHelper.HDel("User:10001", "uname");

            // 获取在哈希表中指定 key 的所有字段和值
            Dictionary<string, string> user10001 = RedisHelper.HGetAll("User:10001");
            foreach (var item in user10001)
            {
                string key = item.Key;
                string value = item.Value;
            }

            // 获取所有哈希表中的字段 [虽然使用HGetAll可以取出所有的value，但是有时候散列包含的值可能非常大，容易造成服务器的堵塞，为了避免这种情况，我们可以使用HKeys取到散列的所有键(HVals可以取出所有值)，然后再使用HGet方法一个一个地取出键对应的值。]
            string[] fields = RedisHelper.HKeys("User:10001");
            foreach (string item in fields)
            {
                string val = RedisHelper.HGet("User:10001", item);
            }

            // 获取哈希表中所有的值
            string[] vals = RedisHelper.HVals("User:10001");

            //var keyValues = dic.Select(a => new [] { a.Key, a.Value.ToString() }).SelectMany(a => a).ToArray();
            string[] user2 = new string[] { "uname", "gmd", "upwd", "123" };
            // 同时将多个field-value（域-值）对设置到哈希表 key 中
            bool set_hash_user2 = RedisHelper.HMSet("User:10002", user2);

            string[] user_get2 = new string[] { "uname", "upwd", "sj" };
            // 获取存储在哈希表中多个字段的值
            string[] user_val2 = RedisHelper.HMGet("User:10002", user_get2); // #gmd,123,

            // 对散列中的值进行自增、自减操作
            bool set_hash_user1_usex = RedisHelper.HSet("User:10003", "uage", "23");

            // 为哈希表 key 中的指定字段和整数值加上增量（increment）自增（正数），自减（负数）
            long hIncrBy = RedisHelper.HIncrBy("User:10001", "uage", 2);

        }

        //列表
        public static void list()
        {
            // 将一个或多个值插入到列表头部
            string[] lpush1 = new string[] { "003", "004" };
            long len1 = RedisHelper.LPush("list", "000");
            long len2 = RedisHelper.LPush("list", "001", "002");
            long len3 = RedisHelper.LPush("list", lpush1);

            // 在列表中添加一个或多个值 [列表尾部]
            long len4 = RedisHelper.RPush("list", "010");

            // 移除并获取列表的第一个元素
            string val1 = RedisHelper.LPop("list");

            // 移除并获取列表的最后一个元素
            string val2 = RedisHelper.RPop("list");

            RedisHelper.BRPop(30, "list_another");
            
            // 获取列表指定范围内的元素[key, start, stop]
            string[] lrang1 = RedisHelper.LRange("list", 0, 2); // #左侧开始，获取前3个元素
            string[] lrang2 = RedisHelper.LRange("list", 0, -1); // #左侧开始，获取全部元素

            // 将 list 最后一个元素弹出并压入 list_another 的头部 [只有一个元素的改变，源列表会少一个元素，目标列表多出一个元素]
            RedisHelper.RPopLPush("list", "list_another");
            RedisHelper.Expire("list_another", 30);

        }

        //有序集合
        public static void set()
        {
            // 向集合添加一个或多个成员 [返回添加成功个数]
            long sadd1 = RedisHelper.SAdd("my_set", "qaz");
            long sadd2 = RedisHelper.SAdd("my_set", "tgb", "yhn");
            string[] set1 = new string[] { "wsx", "edc", "rfv" };
            long sadd3 = RedisHelper.SAdd("my_set", set1);

            // 判断 member 元素是否是集合 key 的成员
            bool isMember = RedisHelper.SIsMember("my_set", "qaz");

            // 返回集合中的所有成员
            string[] members = RedisHelper.SMembers("my_set");

            // 返回集合中的一个随机成员
            string member1 = RedisHelper.SRandMember("my_set");

            // 移除集合中一个或多个成员
            long sRem = RedisHelper.SRem("my_set", "qaz");

            // 移除并返回集合中一个随机成员
            string member2 = RedisHelper.SPop("my_set");

            // 对两个集合进行交、并、差操作
            RedisHelper.SAdd("set-a", "item1", "item2", "item3", "item4", "item5");
            RedisHelper.SAdd("set-b", "item2", "item5", "item6", "item7");

            // 差集
            RedisHelper.SDiff("set-a", "set-b"); // "item1", "item3","item4"

            // 交集
            RedisHelper.SInter("set-a", "set-b"); // "item2","item5"

            // 并集
            RedisHelper.SUnion("set-a", "set-b"); // "item1","item2","item3","item4","item5","item6","item7"

            //#另外还可以用SDiffStore,SInterStore,SUnionStore将操作后的结果存储在新的集合中。

        }

        //有序集合
        public static void zset()
        {
            // 向有序集合添加一个或多个成员，或者更新已存在成员的分数
            RedisHelper.ZAdd("sorted_set", (1, "beijing"));
            RedisHelper.ZAdd("sorted_set", (2, "shanghai"), (3, "shenzhen"));
            (decimal, object)[] set1 = new (decimal, object)[] { (4, "guangzhou"), (5, "tianjing"), (6, "chengdu") };
            RedisHelper.ZAdd("sorted_set", set1);

            // 有序集合中对指定成员的分数加上增量 increment
            decimal incr = RedisHelper.ZIncrBy("sorted_set", "beijing", -2);

            // 通过索引区间返回有序集合成指定区域内的成员，分数从低到高 [key, start, stop]
            string[] zRange1 = RedisHelper.ZRange("sorted_set", 0, 2);
            string[] zRange2 = RedisHelper.ZRange("sorted_set", 0, -1); // #stop=-1返回全部

            // 返回有序集合中指定区域内的成员，通过索引，分数从高到底 [key, start, stop]
            string[] zRevRange1 = RedisHelper.ZRevRange("sorted_set", 0, 2);
            string[] zRevRange2 = RedisHelper.ZRevRange("sorted_set", 0, -1); // #stop=-1返回全部

            // 移除有序集合中一个或多个成员
            RedisHelper.ZRem("sorted_set", "shenzhen");

            // 获取有序集合的成员数量
            long number = RedisHelper.ZCard("sorted_set");

            // 通过分数返回有序集合指定区间内的成员
            string[] ZRangByScore1 = RedisHelper.ZRangeByScore("sorted_set", 2, 4);

            // 通过索引区间返回有序集合成指定区间内的成员和分数
            (string member, decimal score)[] sets = RedisHelper.ZRangeWithScores("Quiz", 0, -1);

        }

        //经纬度
        public static void Geo()
        {
            //1. 添加地点经纬度 [存储到 sorted set 中]
            RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(116.20), Convert.ToDecimal(39.56), "北京");
            RedisHelper.GeoAdd("myLocation", Convert.ToDecimal(120.51), Convert.ToDecimal(30.40), "上海");

            //2. 求两点之间的距离
            var d1 = RedisHelper.GeoDist("myLocation", "北京", "上海", CSRedis.GeoUnit.km);
        }

        //事务
        public static void Pipe()
        {
            // 开启事务
            var pipe = RedisHelper.StartPipe();

            //中间对redis进行操作
            pipe.Set("pipe1", "wsx");

            // 提交
            pipe.EndPipe();

        }

        // 发布订阅
        public static void SubPub()
        {
            //Thread.CurrentThread.ManagedThreadId
            Console.WriteLine($"main-thread:{Environment.CurrentManagedThreadId}");
            
            //普通订阅
            redisClient.Subscribe(
                ("chan1", msg => Console.WriteLine($"chan1-thread:{Environment.CurrentManagedThreadId},{msg.Body}")), 
                ("chan2", msg => Console.WriteLine($"chan2-thread:{Environment.CurrentManagedThreadId},{msg.Body}")
            ));

            //模式订阅（通配符）
            redisClient.PSubscribe(new[] { "test*", "*test001", "test*002" }, msg => {
                Console.WriteLine($"PSUB  {msg.MessageId}:{msg.Body}  {msg.Pattern}: chan:{msg.Channel}");
            });
            //模式订阅已经解决的难题：
            //1、分区的节点匹配规则，导致通配符最大可能匹配全部节点，所以全部节点都要订阅
            //2、本组 "test*", "*test001", "test*002" 订阅全部节点时，需要解决同一条消息不可执行多次
            
            //发布
            redisClient.Publish("chan1", "xiaoming");
            redisClient.Publish("chan2", "xiaohua");
            
            Console.WriteLine("ok");
            Console.ReadLine();
        }

        public static void sub()
        {
            RedisHelper.Subscribe(("channel", msg =>
            {
                
            }));
            RedisHelper.Publish("channel", "demo");
        }

        //缓存壳
        //static void shell()
        //{
        //    //不加缓存的时候，要从数据库查询
        //    var t1 = Test.Select.WhereId(1).ToOne();

        //    //一般的缓存代码，如不封装还挺繁琐的
        //    var cacheValue = rds.Get("test1");
        //    if (!string.IsNullOrEmpty(cacheValue))
        //    {
        //        try
        //        {
        //            return JsonConvert.DeserializeObject(cacheValue);
        //        }
        //        catch
        //        {
        //            //出错时删除key
        //            rds.Remove("test1");
        //            throw;
        //        }
        //    }
        //    var t1 = Test.Select.WhereId(1).ToOne();
        //    rds.Set("test1", JsonConvert.SerializeObject(t1), 10); //缓存10秒

        //    //使用缓存壳效果同上，以下示例使用 string 和 hash 缓存数据
        //    var t1 = rds.CacheShell("test1", 10, () => Test.Select.WhereId(1).ToOne());
        //    var t2 = rds.CacheShell("test", "1", 10, () => Test.Select.WhereId(1).ToOne());
        //    var t3 = rds.CacheShell("test", new[] { "1", "2" }, 10, notCacheFields => new[] {
        //     ("1", Test.Select.WhereId(1).ToOne()),
        //     ("2", Test.Select.WhereId(2).ToOne())
        //    });
        //}

        static void database()
        {
            var connectionString = "127.0.0.1:6379,password=123,poolsize=10,ssl=false,writeBuffer=10240,prefix=key前辍";
            var redis = new CSRedisClient[14]; //定义成单例
            for (var a = 0; a < redis.Length; a++) redis[a] = new CSRedisClient(connectionString + "; defualtDatabase=" + a);

            //访问数据库1的数据
            redis[1].Get("test1");
        }

        //使用管道模式，打包多条命令一起执行，从而提高性能
        static void Pipes()
        {
            var ret1 = redisClient.StartPipe().Set("a", "1").Get("a").EndPipe();
            Console.WriteLine($"{string.Join(",", ret1)}");
            var ret2 = redisClient.StartPipe(p => p.Set("a", "1").Get("a"));
            Console.WriteLine($"{string.Join(",", ret2)}");

            var ret3 = redisClient.StartPipe().Get("b").Get("a").Get("a").EndPipe();
            Console.WriteLine($"{string.Join(",", ret3[0] == null, ret3[1], ret3[2])}");

            Console.WriteLine("ok");
            Console.ReadLine();
        }

        //锁
        public static void locks()
        {
            var lockKey = "lockKey";
            var stockKey = "stock";
            redisClient.Set(stockKey, 5);//商品库存
            var releaseLockScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//释放锁的redis脚本

            redisClient.Del(lockKey);//测试前,先把锁删了
            // 10个线程
            Parallel.For(0, 10, i =>
            {
                var id = Guid.NewGuid().ToString("N");
                //获取锁
                do
                {
                    //set : key存在则失败,不存在才会成功,并且过期时间5秒
                    var success = redisClient.Set(lockKey, id, expireSeconds: 5, exists: RedisExistence.Nx);
                    if (success == true)
                    {
                        break;
                    }
                    Thread.Sleep(TimeSpan.FromSeconds(1));//休息1秒再尝试获取锁
                } while (true);

                Console.WriteLine($"线程:{Task.CurrentId} 拿到了锁,开始消费");

                //扣减库存
                var currentStock = redisClient.IncrBy(stockKey, -1);
                if (currentStock < 0)
                {
                    Console.WriteLine($"库存不足,线程:{Task.CurrentId} 抢购失败!");
                    redisClient.Eval(releaseLockScript, lockKey, id);
                    return;
                }

                //模拟处理业务,这里不考虑失败的情况
                Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 3)));

                Console.WriteLine($"线程:{Task.CurrentId} 消费完毕!剩余 {currentStock} 个");

                //业务处理完后,释放锁.
                redisClient.Eval(releaseLockScript, lockKey, id);
            });
        }

        public static void Set(int dbid)
        {
            using var sentinel = new RedisSentinelManager(false, new string[] { "127.0.0.1:26379" });
            sentinel.Connect("master"); // 打开主数据库连接，参数是主数据库别称，在设置哨兵的时候有设置
            var test2 = sentinel.Call(t => t.Select(dbid)); // 使用Call方法可以运行当前主数据的操作，select方法是选择数据库0-14号进行操作
            sentinel.Call(t => t.Set("haha", DateTime.Now.ToString()));//执行方法
        }
        
        public void Queue()
        {
            
        }
    }
}
